package gbench.appdemo.mall.erp.components;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.Term.FT;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.matlib.rdd.BasePair;
import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.SQL;

/**
 * 
 * @author gbench
 *
 */
public interface IEnterprise extends IMyERPEntity {

    /**
     * 
     * @return
     */
    public Integer getId();

    /**
     * 名称
     * 
     * @return
     */
    public String getName();

    public IStore getStore();
    
    /**
     * 设置 产品目录
     * 
     * @param catalog 产品目录
     * @return 企业对象本身
     */
    public IEnterprise setDefaultCatalog(final ICatalog catalog);

    /**
     * 获取默认产品
     * 
     * @return 产品目录
     */
    public ICatalog getDefaultCatalog();

    /**
     * 企业初始化
     * 
     * @return
     */
    public default IEnterprise initialize() {
        this.getStore().initialize();
        return this;
    }

    /**
     * 
     * @param param 价格表参数：整数类型的catalogId 或是 IRecord类型的 产品目录的元数据 {name:名称,symbol:产品目录符号,description:产品目录说明,version:目录版本}
     * @param price_ratio 价格浮动比率 价格=产品基准价格*(1+price_ratio)
     * @param tax_ratio 税率
     * @param productPrices 产品价格清单
     * @return 产品目录
     */
    public default <P1 extends IProduct, P2 extends IPrice> ICatalog buildCatalog(final Object param, double price_ratio, double tax_ratio,
            final Iterable<BasePair<P1, P2>> productPrices) {
        Integer _catalogId = null;
        String _name = null;
        String _symbol = null;
        String _version = null;
        String _description = null;
        
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        
        if (param instanceof Number) { // 价格表id
            _catalogId = ((Number) param).intValue();
        } else if (param instanceof IRecord) { // 产品目录的元数据 {name:名称,symbol:产品目录符号,description:产品目录说明,version:目录版本}
            final var uuid = UUID.randomUUID();
            final var rec = (IRecord) param;
            _name = rec.str("name", "产品目录-" + uuid);
            _symbol = rec.str("symbol", "CATALOG-" + uuid);
            _description = rec.str("description", FT("$0-$1", this.getName(), _name));
            _version = rec.str("version", "1.1");
            
            final var sql = FT("select * from t_catalog where (name='$0' and symbol='$1') and ownerid=$2 ", _name, _symbol, this.getId());
            final var opt = dataApp.sql2maybe(sql);
            _catalogId = opt.map(e->e.i4("id")).orElse(null); // 提取产品目录
        
        } else {
            println("参数非法");
            return null;
        }
        
        final var catalogId = _catalogId;
        final var catalog_name = _name;
        final var catalog_symbol = _symbol;
        final var catalog_version = _version;
        final var catalog_description = _description;
        final var quoteItems = StreamSupport.stream(productPrices.spliterator(),false).map( bp -> REC(
                "product", bp._1(), 
                "price", bp._2().mul(1 + price_ratio).getAmount(), 
                "tax", tax_ratio)
        );
        final var ar = new AtomicReference<ICatalog>();
        
        // 制作产品目录
        dataApp.withTransaction(sess -> {
            final Optional<IRecord> catalog_opt = catalogId == null 
                    ? Optional.empty()
                    : sess.sql2maybe(FT("select * from t_catalog where id=$0 and ownerid = $1 ", catalogId, this.getId()));
            final Function<IRecord, ICatalog> catalog_mutator = rec -> {
                final var _catalog = rec.mutate(erpApp.ctor(ICatalog.class));
                _catalog.setAttributes(rec);
                return _catalog;
            };
            ICatalog catalog = catalog_opt.map(catalog_mutator).orElse(null); // 产品目录
            
            if (catalog_opt.isEmpty()) {
                final var create_time = LocalDateTime.now();
                final var catalog_attrs = REC("name", catalog_name, "symbol", catalog_symbol, "path",
                        "D:/sliced/files/products/valuables/gold.jpg", "version", catalog_version, "flag", 0,
                        "ownerid", this.getId(), "description", catalog_description, "price", 1, "create_time", create_time); // 价格目录信息
                final var ins_catalog_sql = SQL.of("t_catalog", catalog_attrs).insert(); // 数据插入 sql
                final var catalog_id = sess.sql2execute2int(ins_catalog_sql); // 获得生成的 项目id
                catalog = catalog_attrs.derive("id", catalog_id).mutate(catalog_mutator); // 变换成 产品目录对象
            } // if catalog_opt
            
            final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0 and item_id=$1", this.getId(), catalog.getId());
            final var opt = sess.sql2maybe(sql);
            if (opt.isEmpty()) { // 企业添加产品目录
                final var optional = this.getName() + "-产品目录-" + catalog.getName();
                final var create_time = LocalDateTime.now(); // 创建时间
                final var entity_item_rel = REC("name", "t_fortune/t_catalog", "path",
                        "D:/sliced/files/products/valuables/gold.jpg", "entity_id", this.getId(), "item_id",
                        catalog.getId(), "optional", optional, // 基本关系
                        "drcr", -1, "quantity", -1, "amount", -1, "flag", 0, "description", "产品目录", "create_time", create_time);
                // println(entity_item_rel);
                sess.sql2execute2int(SQL.of("t_entity_item_rel", entity_item_rel).insert());
            } else { // if
                println(FT("$0-$1 的catalog已经存在",this.getId(),catalog.getId()));
            }
            
            catalog.add(quoteItems.collect(Collectors.toList()));
            ar.set(catalog); // 设置产品目录
        }); // withTransaction
        
        return ar.get();
    }
    
    /**
     * 获取 产品目录
     * 
     * @param catalogName 产品目录名
     * @return 产品目录名
     */
    public default ICatalog catalogOf(final String catalogName) {
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.getId());
        final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.name = '$1'", sql,catalogName);
        // println(_sql);
        return dataApp.sql2maybe(_sql).map(erpApp.ctor(ICatalog.class)).orElse(null);
    }

    
    /**
     * 获取产品目录
     * 
     * @return
     */
    public default List<ICatalog> getCatalogs() {
        final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.getId());
        final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id", sql);
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        return dataApp.sqlqueryS(_sql).map(erpApp.ctor(ICatalog.class)).collect(Collectors.toList());
    }
    
    /**
     * 获取产品目录
     * 
     * @return
     */
    public default Optional<ICatalog> getCatalog(final int catalog_id) {
        final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.getId());
        final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.id = $1", sql,catalog_id);
        // println(_sql);
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        return dataApp.sql2maybe(_sql).map(erpApp.ctor(ICatalog.class));
    }
    
    /**
     * getDefaultCatalog 的别名 <br>
     * 获取默认产品
     * 
     * @return 产品目录
     */
    public default ICatalog getCatalog() {
        return this.getDefaultCatalog();
    }
    
    /**
     * 获取 产品目录
     * 
     * @param catalogName 产品目录名
     * @return 产品目录名
     */
    public default Optional<ICatalog> getCatalog(final String catalogName) {
        final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.getId());
        final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.name = '$1'", sql,catalogName);
        // println(_sql);
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        return dataApp.sql2maybe(_sql).map(erpApp.ctor(ICatalog.class));
    }
    
    
    /**
     * 获取 产品报价清单
     * 
     * @param catalogName 产品目录名
     * @return 产品报价清单
     */
    public default XVec<IPriceQuote> catalogX(final String catalogName) {
        final var catalog = this.catalogOf(catalogName);
        if (catalog == null) return null;
        return catalog.collect(XVec.xveclc());
    }

    /**
     * 获取销售订单
     * @return
     */
    public default List<ISaleOrder> getSaleOrders() {
        final var ar = new AtomicReference<List<ISaleOrder>>();
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        dataApp.withTransaction(sess->{
            final var sql = FT("select * from t_contract where partB=$0",this.getId());
            final List<ISaleOrder> ll = sess.sql2stream(sql).map(e->{
                //println(e);
                final var order = erpApp.getOrder(e.i4("id"));
                //println(order);
                return erpApp.saleOrderOf(order);
            }).collect(Collectors.toList());
            ar.set(ll);
        });
        return ar.get();
    }
    
    /**
     * 更新企业的信用额度
     * 
     * @param partB 企业B
     * @param updator 更新函数 r:先前值,新纪录为null->{ return 更新值;};
     */
    public default IEnterprise updateCredit(final Integer partB, final Function<Double, Double> updator) {
        final var partA = this.getId(); // 主键id
        final var name = "t_fortune/t_fortune"; // 企业对企业
        final var optional = "CREDIT_AMOUNT"; // 信用额度
        final var dataApp = this.dataApp();
        
        dataApp.withTransaction(sess -> {
            final var select_sql = FT("select * from t_entity_item_rel where name='$0' and optional='$1' and entity_id='$2'", name, optional, partA);
            final var rec_opt = sess.sql2maybe(select_sql); // 数据检索
            final var pre_credit_amount  = rec_opt.map(e->e.dbl("amount")).orElse(null); // 先前的数值
            final var credit_amount = updator.apply( pre_credit_amount ); // 新的信用额度
            
            if (rec_opt.isEmpty()) { // 数据不存在则插入
                final var create_time = LocalDateTime.now(); // 创建时间
                final var entity_item_rel = REC( // 关系分录
                        "name", name, "path", "D:/sliced/files/products/valuables/gold.jpg", // 明细记录
                        "entity_id", partA, "item_id", partB, "optional", optional, // 关系数据，optional 表示信用额度属性
                        "drcr", -1, "quantity", -1, "amount", credit_amount, "flag", 0, "description", "信用额度表", "create_time", create_time // 借贷分录
                ); // 关系分录
                final var insert_sql = SQL.of("t_entity_item_rel", entity_item_rel).insert(); // 插入语句
                sess.sqlexecute(insert_sql); // 数据插入
            } else { // 数值存在则更新
                final var upt_time = LocalDateTime.now(); // 更新时间
                final var id = rec_opt.map(e -> e.i4("id")).get(); // 记录主键
                final var update_sql = FT( "update t_entity_item_rel set quantity=$1, amount=$2, update_time='$3' where id=$0", id,
                        pre_credit_amount, credit_amount, upt_time ); // 更新语句
                sess.sqlexecute(update_sql); // 数据更新
            } // if rec_opt.isPresent()
        }); // withTransaction
        
        return this;
    }
    
    /**
     * 更新企业的信用额度
     * 
     * @param partB 企业对象
     * @param updator 更新函数 r:先前值,新纪录为null->{ return 更新值;};
     */
    public default IEnterprise updateCredit(final IEnterprise partB, final Function<Double, Double> updator) {
        return this.updateCredit(partB.getId(), updator);
    }
    
    /**
     * 获取一个企业的信用额度
     * 
     * @param partB 客户企业
     * @return 信用额度
     */
    public default Double getCredit2(final IEnterprise partB) {
        return this.getCredit(partB.getId()).orElse(0d);
    }

    /**
     * 获取一个企业的信用额度
     * 
     * @param partB 客户企业
     * @return 信用额度
     */
    public default Double getCredit2(final Integer partB) {
        return this.getCredit(partB).orElse(0d);
    }

    /**
     * 获取一个企业的信用额度
     * 
     * @param partB 客户企业
     * @return 信用额度
     */
    public default Optional<Double> getCredit(final IEnterprise partB) {
        return this.getCredit(partB.getId());
    }

    /**
     * 获取一个企业的信用额度
     * 
     * @param partB 客户企业
     * @return 信用额度
     */
    public default Optional<Double> getCredit(final Integer partB) {
        final var partA = this.getId();
        final var name = "t_fortune/t_fortune"; // 企业对企业
        final var optional = "CREDIT_AMOUNT"; // 信用额度标记
        final var select_sql = FT(
                "select * from t_entity_item_rel where name='$0' and optional='$1' and entity_id='$2' and item_id='$3'",
                name, optional, partA, partB);
        final var dataApp = this.dataApp();
        
        return dataApp.sql2maybe(select_sql).map(e -> e.dbl("amount"));
    }
    
    /**
     * 销售订单
     * 
     * @param buyer 客户
     * @param items 产品订单
     * @return 销售订单
     */
    public default ISaleOrder sale(final IEnterprise buyer, final ILineItem... items) {
        final var erpApp = this.erpApp();
        final var so = erpApp.saleOrderOf(buyer, this);
        so.addAll(Arrays.asList(items));
        return so;
    }
    
    /**
     * 销售订单
     * 
     * @param buyer 客户
     * @param items 产品订单
     * @return 销售订单
     */
    public default ISaleOrder sale(final IEnterprise buyer, final Iterable<ILineItem>items) {
        return this.sale(buyer, new XVec<>(items).toArray());
    }
    
    /**
     * 采购订单
     * 
     * @param supplier 供应商
     * @param items    产品列表
     * @return 采购订单
     */
    public default IPurchaseOrder purchase(final IEnterprise supplier, final ILineItem... items) {
        final var erpApp = this.erpApp();
        final var po = erpApp.purchaseOrderOf(this, supplier);
        po.addAll(Arrays.asList(items));
        return po;
    }
    
    /**
     * 采购订单
     * 
     * @param supplier 供应商
     * @param items    产品列表
     * @return 采购订单
     */
    public default IPurchaseOrder purchase(final IEnterprise supplier, final Iterable<ILineItem>items) {
        return this.purchase(supplier, new XVec<>(items).toArray());
    }
    
    /**
     * 
     * @return 采购订单
     */
    public default List<IPurchaseOrder> getPuchaseOrders() {
        final var ar = new AtomicReference<List<IPurchaseOrder>>();
        final var dataApp = this.dataApp();
        final var erpApp = this.erpApp();
        dataApp.withTransaction(sess->{
            final var sql = FT("select * from t_contract where partA=$0",this.getId());
            //println(sql);
            final List<IPurchaseOrder> ll = sess.sql2stream(sql).map(e->{
                final var order = erpApp.getOrder(e.i4("id"));
                return erpApp.purchaseOrderOf(order);
            }).collect(Collectors.toList());
            ar.set(ll);
        });
        return ar.get();
    }
    
    /**
     * 默认库房
     * @return
     */
    public default IWareHouse getDefualtHouse() {
        return this.getStore().getDefaultWareHouse();
    }
    
    /**
     * 获取企业的银行的账户余额,合并多个银行 为一行科目 即 银行合列
     * 
     * @param banks 银行列表
     * @return DFrame
     */
    public default DFrame trialBalance(final IBank ... banks) {
        return this.trialBalance(true,banks); // 银行合列
    }
    
    /**
     * 获取企业的银行的账户
     * 
     * @param b 是否合并多个银行 为一行科目, false 银行分列, true 银行合列
     * @param banks 银行列表
     * @return DFrame
     */
    public default DFrame trialBalance(final boolean b,final IBank ... banks) {
        final var bankIds = Stream.of(banks).map(IBank::getId).map(e->"'"+e+"'").collect(Collectors.joining(","));
        final var flds = "ref bankid, entity_id tx, drcr, item_id acctid, if(drcr=0,1,-1)*amount amount ";
        final var sql0 = FT("select $0 from t_entity_item_rel where name='t_transaction/t_coa' and ref in ($1) and optional='$2'",flds,bankIds,this.getId());
        final var sql1 = FT("select $0 bankid, acctid, sum(amount) amount from ($1) t1 group by $2", b ? bankIds.replace("','", ",") : "bankid", sql0, !b ? "bankid,acctid" : "acctid");
        final var sql2 = FT("select bankid, acctid, c.name acct, amount from ($0) t3 inner join t_coa c on t3.acctid = c.code ",sql1);
        final var sql = sql2;
        // println(sql);
        final var dataApp = this.dataApp();
        return dataApp.sqlqueryS(sql).collect(DFrame.dfmclc);
    }
    
    /**
     * 会计记账
     * 
     * @param recs 复式记账
     * @return FinanceEntity
     */
    @SuppressWarnings("unchecked")
    public default IFinanceEntity<IEnterprise> withAcctTransaction(final IRecord... recs) {

        final var finance_entity = this.getFinanceEntity();
        final var acct_txs = Stream.of(recs).map(e -> BasePair.bp(finance_entity, e)).toArray(BasePair[]::new);

        this.erpApp().withAcctTransaction(acct_txs);

        return finance_entity;
    }
    
    /**
     * 
     * @return
     */
    public default IFinanceEntity<IEnterprise> getFinanceEntity() {
        return this.erpApp().getBank(this.getId(), false);
    }
}
